package com.ekuaitu.kuaitustaff.utils.ble.manager;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothGattService;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;

import com.ekuaitu.kuaitustaff.utils.ble.bean.ConnectError;
import com.ekuaitu.kuaitustaff.utils.ble.callback.IConnectCallbackInner;
import com.ekuaitu.kuaitustaff.utils.ble.callback.data.IBleCallback;
import com.ekuaitu.kuaitustaff.utils.ble.callback.data.ICharacteristicCallback;
import com.ekuaitu.kuaitustaff.utils.ble.callback.data.ICharacteristicCallbackInner;
import com.ekuaitu.kuaitustaff.utils.ble.common.State;
import com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant;
import com.ekuaitu.kuaitustaff.utils.ble.exception.ConnectException;
import com.ekuaitu.kuaitustaff.utils.ble.exception.GattException;
import com.ekuaitu.kuaitustaff.utils.ble.exception.InitiatedException;
import com.ekuaitu.kuaitustaff.utils.ble.exception.OtherException;
import com.ekuaitu.kuaitustaff.utils.ble.exception.TimeoutException;
import com.ekuaitu.kuaitustaff.utils.ble.utils.HexUtil;
import com.ekuaitu.kuaitustaff.utils.ble.utils.Logger;
import com.ekuaitu.kuaitustaff.utils.ble.utils.StringUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.LinkedBlockingQueue;

import static com.ekuaitu.kuaitustaff.utils.ble.BleConstants.ZhiXingTong.readCharacteristicUUID;
import static com.ekuaitu.kuaitustaff.utils.ble.BleConstants.ZhiXingTong.readServiceUUID;
import static com.ekuaitu.kuaitustaff.utils.ble.BleConstants.ZhiXingTong.writeCharacteristicUUID;
import static com.ekuaitu.kuaitustaff.utils.ble.BleConstants.ZhiXingTong.writeServiceUUID;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.DEFAULT_CONN_TIME;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.DEFAULT_OPERATE_TIME;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.MSG_CONNECTION_STATE_CHANGE;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.MSG_CONNECT_TIMEOUT;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.MSG_DISCONNECT_TIMEOUT;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.MSG_DISCOVERING_SERVICE;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.MSG_SERVICES_DISCOVERED;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.MSG_UNDISCOVERING_SERVICE;
import static com.ekuaitu.kuaitustaff.utils.ble.common.ZXTBleConstant.MSG_WRITE_CHA;

/**
 * Created by WAHAHA on 2018/5/18.
 */
public class BluetoothConnectionController {

    private static final String TAG = BluetoothConnectionController.class.getSimpleName();
    private State state = State.DISCONNECT;
    private int discoverServiceRetry = 3;//服务重试次数
    private int discoverServicesDelay = 500;
    private int discoverServicesHold = 2000;
    private int disconnectHold = 5000;
    private int sendDataDelay = 1000;
    private IConnectCallbackInner connectCallback;
    private ICharacteristicCallbackInner receiveCallback;
    private int connectTimeout = DEFAULT_CONN_TIME;
    private int operateTimeout = DEFAULT_OPERATE_TIME;
    private BluetoothGattCallback coreGattCallback;
    private volatile Set<IBleCallback> bleCallbacks = new LinkedHashSet<>();
    private IBleCallback tempBleCallback;
    private LinkedBlockingQueue<byte[]> dataInfoQueue = new LinkedBlockingQueue<>();
    private BluetoothGatt bluetoothGatt;
    private boolean abortExecute = false;
    private boolean callDisconnect = false;


    private Handler mHandler = new Handler(Looper.getMainLooper()) {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MSG_DISCOVERING_SERVICE:
                    //开始发现服务
                    discoverServices((BluetoothGatt) msg.obj, msg.arg1, msg.arg2);
                    break;
                case MSG_UNDISCOVERING_SERVICE:
                    //未发现服务
                    unDiscoverServices((BluetoothGatt) msg.obj, msg.arg1, msg.arg2);
                    break;
                case MSG_CONNECT_TIMEOUT:
                    //连接超时
                    connectTimeout(msg);
                    break;
                case MSG_CONNECTION_STATE_CHANGE:
                    //连接状态改变的回调
                    onConnectionStateChange((ConnectError) msg.obj, msg.arg1, msg.arg2);
                    break;
                case MSG_DISCONNECT_TIMEOUT:
                    //断开连接超时，则认为断开连接了
                    Logger.e(TAG, "MSG_DISCONNECT_TIMEOUT");
                    close((BluetoothGatt) msg.obj);
                    break;
                case MSG_SERVICES_DISCOVERED:
                    //找到服务
                    onServicesDiscovered((BluetoothGatt) msg.obj, msg.arg1);
                    break;
                case MSG_WRITE_CHA:
                    IBleCallback bleCallback = (IBleCallback) msg.obj;
                    if (bleCallback != null) {
                        bleCallback.onFailure(new TimeoutException());
                        removeBleCallback(bleCallback);
                    }
                    break;
            }

        }
    };

    private synchronized void removeBleCallback(IBleCallback bleCallback) {
        if (bleCallbacks != null && bleCallbacks.size() > 0) {
            bleCallbacks.remove(bleCallback);
        }
    }

    public BluetoothConnectionController(IConnectCallbackInner connectCallback, ICharacteristicCallbackInner receiveCallback) {
        this.connectCallback = connectCallback;
        this.receiveCallback = receiveCallback;
        this.discoverServiceRetry = 3;
        this.abortExecute = false;
        this.state = State.DISCONNECT;
        this.bleCallbacks.clear();
        this.bluetoothGatt = null;
    }

    private synchronized void onConnectionStateChange(final ConnectError connectError, final int status, final int newState) {
        //已经连接成功，移除连接超时的timer
        Logger.e(TAG, "MSG_CONNECTION_STATE_CHANGE");
        mHandler.removeMessages(MSG_CONNECT_TIMEOUT);

        Logger.e(TAG, "onConnectionStateChange   bluetooGatt == gatt ; " + (bluetoothGatt == connectError.getBluetoothGatt()));

        if (status == BluetoothGatt.GATT_SUCCESS) {
            if (newState == BluetoothGatt.STATE_CONNECTED) {
                Logger.e(TAG, "已连接,gatt.discoverServices()开启服务发现");

                if (state == State.CONNECT_TIMEOUT || abortExecute) {
                    state = State.DISCONNECT_WAIT;
                    disconnect(connectError.getBluetoothGatt());
                    mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISCONNECT_TIMEOUT, connectError.getBluetoothGatt()), disconnectHold);
                } else {
                    state = State.DISCOVER_SERVICES_WAIT;
                    mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISCOVERING_SERVICE, status, newState, connectError.getBluetoothGatt()), discoverServicesDelay);
                }

            } else if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                Logger.e(TAG, "已经断开连接");
                //防止连上之后立即失败
                mHandler.removeMessages(MSG_UNDISCOVERING_SERVICE);
                mHandler.removeMessages(MSG_DISCOVERING_SERVICE);
                close(connectError.getBluetoothGatt());
                state = State.DISCONNECT;
                //主动断开
                if (!abortExecute) {
                    if (connectError.getConnectCallback() != null) {
                        connectError.getConnectCallback().onDisconnect(callDisconnect);
                    }
                } else {
                    Logger.e(TAG, "abortExecute = true, 不回调");
                }

            } else if (newState == BluetoothGatt.STATE_CONNECTING) {
                state = State.CONNECT_PROCESS;
            }
        } else {
            //防止出现133和其他位置code
            Logger.e(TAG, "连接失败:" + status);
            state = State.CONNECT_FAILURE;
            //防止连上之后立即失败
            mHandler.removeMessages(MSG_UNDISCOVERING_SERVICE);
            mHandler.removeMessages(MSG_DISCOVERING_SERVICE);
            close(connectError.getBluetoothGatt());
            if (!abortExecute) {
                if (connectError.getConnectCallback() != null) {
                    connectError.getConnectCallback().onConnectFailure(new ConnectException(connectError.getBluetoothGatt(), status));
                }
            } else {
                Logger.e(TAG, "abortExecute = true, 不回调");
            }

        }

    }


    /**
     * 连接超时
     */
    private void connectTimeout(Message msg) {
        Logger.e("MSG_CONNECT_TIMEOUT 连接超时");
        ConnectError connectTimeout = (ConnectError) msg.obj;
        if (connectTimeout != null) {
            //连接超时，其实无需重连了。连接异常需要重新连接
            state = State.CONNECT_TIMEOUT;
            IConnectCallbackInner connectCallback = connectTimeout.getConnectCallback();
            if (connectCallback != null && !abortExecute) {
                stopCallBack();
                connectCallback.onConnectTimeout(connectTimeout.getBluetoothGatt());
            }
        }
    }


    private void unDiscoverServices(final BluetoothGatt gatt, final int status, final int newState) {
        Logger.e(TAG, "没有发现蓝牙服务");
        if (abortExecute) {
            discoverServiceRetry = -1;
        }
        if (discoverServiceRetry > 0) {
            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISCOVERING_SERVICE, status, newState, gatt), discoverServicesDelay);
        } else {
            //断开连接
            stopCallBack();
            connectCallback.onUnDiscoverServices(gatt);
            disconnect(gatt);
            state = State.DISCONNECT_WAIT;
            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISCONNECT_TIMEOUT, gatt), disconnectHold);
        }
    }

    private void discoverServices(final BluetoothGatt gatt, final int status, final int newState) {
        state = State.DISCOVERING_SERVICES;
        discoverServiceRetry--;
        Logger.e(TAG, "第 " + (3 - discoverServiceRetry) + "次开启服务");
        gatt.discoverServices();
        mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_UNDISCOVERING_SERVICE, status, newState, gatt), discoverServicesHold);

    }

    @TargetApi(18)
    public void disconnect(BluetoothGatt gatt) {
        if (Build.VERSION.SDK_INT >= 18) {
            if (gatt != null) {
                Logger.e("gatt.disconnect()");
                callDisconnect = true;
                gatt.disconnect();
            } else {
                if (bluetoothGatt != null) {
                    callDisconnect = true;
                    bluetoothGatt.disconnect();
                } else {
                    Logger.e("disconnect() 未执行 , 因为 bluetoothGatt==null ");
                }
            }
        }
    }


    @TargetApi(18)
    public void close(BluetoothGatt gatt) {
        if (Build.VERSION.SDK_INT >= 18) {
            connectCallback.onRelease(gatt);
            if (gatt != null) {
                refreshDeviceCache(gatt);
                Logger.e("gatt.close()");
                gatt.close();
                bluetoothGatt = null;
            } else {
                if (bluetoothGatt != null) {
                    refreshDeviceCache(bluetoothGatt);
                    bluetoothGatt.close();
                    bluetoothGatt = null;
                } else {
                    Logger.e("close() 未执行 , 因为 bluetoothGatt==null ");
                }
            }

            if (bleCallbacks != null) {
                bleCallbacks.clear();
            }

        }
    }


    @TargetApi(18)
    public boolean refreshDeviceCache(BluetoothGatt gatt) {
        if (Build.VERSION.SDK_INT < 18) {
            return false;
        }
        try {
            final Method refresh = BluetoothGatt.class.getMethod("refresh");
            if (refresh != null && gatt != null) {
                final boolean success = (Boolean) refresh.invoke(gatt);
                Logger.e("Refreshing result: " + success);
                return success;
            }
        } catch (Exception e) {
            Logger.e("An exception occured while refreshing device" + e);
        }
        return false;
    }

    private void onServicesDiscovered(final BluetoothGatt gatt, final int status) {

        if (!abortExecute) {
            if (status == BluetoothGatt.GATT_SUCCESS) {
                //服务发现成功
                if (connectCallback != null) {
                    boolean isServiceFound = initGattServices(gatt.getServices(), receiveCallback);
                    if (isServiceFound) {
                        state = State.CONNECT_SUCCESS;
                        mHandler.removeMessages(MSG_UNDISCOVERING_SERVICE);
                        mHandler.removeMessages(MSG_DISCOVERING_SERVICE);
                        connectCallback.onConnectSuccess(gatt, status, sendDataDelay);
                    } else {
                        state = State.UNDISCOVERED_SERVICES_UUID;
                        Logger.e(TAG, "服务中没有发现uuid");
                    }
                }
            }
        } else {
            state = State.DISCONNECT_WAIT;
            disconnect(gatt);
            mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_DISCONNECT_TIMEOUT, gatt), disconnectHold);
        }
    }


    private boolean initGattServices(List<BluetoothGattService> gattServices, ICharacteristicCallbackInner receiveCallback) {

        // Loops through available GATT Services.
        Logger.e("initGattServices");
        boolean hasFoundWriteCharacteristic = false;
        boolean hasFoundReadCharacteristic = false;
        for (final BluetoothGattService gattService : gattServices) {
            String uuid = gattService.getUuid().toString();
            if (!StringUtils.isEmpty(uuid)
                    && (uuid.substring(0, uuid.indexOf("-")).toLowerCase()
                    .contains(writeServiceUUID))
                    || uuid.substring(0, uuid.indexOf("-")).toLowerCase().contains(readServiceUUID)) {
                // Loops through available Characteristics.
                final List<BluetoothGattCharacteristic> gattCharacteristics = gattService
                        .getCharacteristics();
                for (final BluetoothGattCharacteristic gattCharacteristic : gattCharacteristics) {
                    if (!StringUtils.isEmpty(gattCharacteristic.getUuid().toString())) {

                        if (gattCharacteristic.getUuid().toString().substring(0, uuid.indexOf("-"))
                                .toLowerCase().contains(writeCharacteristicUUID)) {
                            Logger.e("匹配 writeCharacteristicUUID 成功");
                            BLESendManager.getInstance().setBluetoothGattCharacteristic(
                                    gattCharacteristic);
                            hasFoundWriteCharacteristic = true;
                        }

                        if (gattCharacteristic.getUuid().toString().substring(0, uuid.indexOf("-")).toLowerCase().contains(readCharacteristicUUID)) {
                            Logger.e("匹配 readCharacteristicUUID 成功");
                            enableCharacteristicNotification(gattCharacteristic, false);
                            hasFoundReadCharacteristic = true;
                        }
                    }
                }
            }
            if (hasFoundReadCharacteristic && hasFoundWriteCharacteristic) {
                Logger.e("服务匹配成功");
                return true;
//                break;
            }
        }
        return false;
    }

    @TargetApi(18)
    private boolean enableCharacteristicNotification(BluetoothGattCharacteristic characteristic, boolean isIndication) {
        if (Build.VERSION.SDK_INT < 18) {
            return false;
        }
        if (characteristic != null && (characteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) {
            return setCharacteristicNotification(bluetoothGatt, characteristic, true, isIndication);
        } else {
            if (receiveCallback != null) {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        receiveCallback.onFailure(new OtherException("Characteristic [not supports] readable!"));
                        removeBleCallback(receiveCallback);
                    }
                });
            }
            return false;
        }
    }

    @TargetApi(18)
    private boolean setCharacteristicNotification(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, boolean enable, boolean
            isIndication) {
        if (Build.VERSION.SDK_INT < 18) {
            return false;
        }
        if (gatt != null && characteristic != null) {
            Logger.e("Characteristic set notification value: " + enable);
            boolean success = gatt.setCharacteristicNotification(characteristic, enable);
            if (enable) {
                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString(ZXTBleConstant.CLIENT_CHARACTERISTIC_CONFIG));
                if (descriptor != null) {
                    if (isIndication) {
                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                    } else {
                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                    }
                    gatt.writeDescriptor(descriptor);
                    Logger.e("Characteristic set notification is Success!");
                }
            }
            return success;
        }
        return false;
    }


    /**
     * 开始连接
     */
    public void connect(Context context, BluetoothDevice bluetoothDevice) {

        Logger.e(TAG, "connect()");
        if (coreGattCallback == null) {
            iniGatteCallBack();
        }

        state = State.CONNECT_PROCESS;
        bluetoothGatt = bluetoothDevice.connectGatt(context, false, coreGattCallback);

        //增加超时（连接超时）
        if (mHandler != null) {
            Message msg = mHandler.obtainMessage(MSG_CONNECT_TIMEOUT, new ConnectError(bluetoothGatt, connectCallback));
            mHandler.sendMessageDelayed(msg, connectTimeout);
        }

    }


    @TargetApi(18)
    private void iniGatteCallBack() {
        if (Build.VERSION.SDK_INT >= 18) {
            coreGattCallback = new BluetoothGattCallback() {

                @Override
                public void onConnectionStateChange(final BluetoothGatt gatt, final int status, final int newState) {
                    Logger.e("onConnectionStateChange  status: " + status + " ,newState: " + newState +
                            "  ,threadName: " + Thread.currentThread().getName() + ",threadId:" + Thread.currentThread().getId());
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_CONNECTION_STATE_CHANGE, status, newState, new ConnectError(gatt, connectCallback)));
                }

                @Override
                public void onServicesDiscovered(final BluetoothGatt gatt, final int status) {
                    Logger.e("onServicesDiscovered  status: " + status + "  ,threadName: " + Thread.currentThread().getName() + ",threadId:" + Thread.currentThread().getId());
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_SERVICES_DISCOVERED, status, -1, gatt));

                }

                /**
                 * 写出数据
                 * @param gatt
                 * @param characteristic
                 * @param status
                 */
                @Override
                public void onCharacteristicWrite(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic, final int status) {
                    Logger.e("onCharacteristicWrite  status: " + status + ", data:" + HexUtil.encodeHexStr(characteristic.getValue()));
                    if (bleCallbacks == null) {
                        return;
                    }
                    if (mHandler != null) {
                        mHandler.removeMessages(MSG_WRITE_CHA);
                    }
                    runOnMainThread(new Runnable() {
                        @Override
                        public void run() {
                            for (IBleCallback bleCallback : bleCallbacks) {
                                if (bleCallback instanceof ICharacteristicCallback) {
                                    if (status == BluetoothGatt.GATT_SUCCESS) {
                                        ((ICharacteristicCallback) bleCallback).onSuccess(characteristic.getValue());
                                    } else {
                                        bleCallback.onFailure(new GattException(status));
                                    }
                                }
                            }
                            removeBleCallback(tempBleCallback);
                        }
                    });
                }

                //设备返回数据的回调
                @Override
                public void onCharacteristicChanged(BluetoothGatt gatt, final BluetoothGattCharacteristic characteristic) {
                    dataInfoQueue.offer(characteristic.getValue());
                    Logger.e("onCharacteristicChanged data:" + HexUtil.encodeHexStr(characteristic.getValue()));
                    onDataParserCompleted();
                }

            };
        }
    }

    private void runOnMainThread(Runnable runnable) {
        if (isMainThread()) {
            runnable.run();
        } else {
            if (mHandler != null) {
                mHandler.post(runnable);
            }
        }
    }

    private boolean isMainThread() {
        return Looper.myLooper() == Looper.getMainLooper();
    }

    /**
     * 返回数据处理完成
     */
    public void onDataParserCompleted() {
        runOnMainThread(new Runnable() {
            @Override
            public void run() {
                if (receiveCallback != null) {
                    if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) {
                        receiveCallback.onSuccess(dataInfoQueue.poll());
                    }
                }
            }
        });
    }


    @TargetApi(18)
    public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic, byte[] data, final ICharacteristicCallback bleCallback) {
        if (Build.VERSION.SDK_INT < 18) {
            return false;
        }
        if (characteristic == null) {
            if (bleCallback != null) {
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        bleCallback.onFailure(new OtherException("this characteristic is null!"));
                        removeBleCallback(bleCallback);
                    }
                });
            }
            return false;
        }
        Logger.e(characteristic.getUuid() + " characteristic write bytes: " + Arrays.toString(data) + " ,hex: " + HexUtil.encodeHexStr
                (data));
        listenAndTimer(bleCallback, MSG_WRITE_CHA);
        characteristic.setValue(data);
        return handleAfterInitialed(bluetoothGatt.writeCharacteristic(characteristic), bleCallback);
    }

    private boolean handleAfterInitialed(boolean initiated, final IBleCallback bleCallback) {
        if (bleCallback != null) {
            if (!initiated) {
                if (mHandler != null) {
                    mHandler.removeCallbacksAndMessages(null);
                }
                runOnMainThread(new Runnable() {
                    @Override
                    public void run() {
                        bleCallback.onFailure(new InitiatedException());
                        removeBleCallback(bleCallback);
                    }
                });
            }
        }
        return initiated;
    }

    private synchronized void listenAndTimer(final IBleCallback bleCallback, int what) {
        if (bleCallbacks != null && mHandler != null) {
            this.tempBleCallback = bleCallback;
            bleCallbacks.add(bleCallback);
            Message msg = mHandler.obtainMessage(what, bleCallback);
            mHandler.sendMessageDelayed(msg, operateTimeout);
        }
    }

    public void stopCallBack() {
        abortExecute = true;
    }

    public void clear() {
        if (state == State.CONNECT_SUCCESS) {
            disconnect(null);
        }
    }

    public BluetoothGatt getBluetoothGatt() {
        return bluetoothGatt;
    }
}
